/*******************************************************************************
 * Copyright (c) 2014, 2015 Ericsson and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Marc Khouzam (Ericsson) - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.breakpoints; 

import org.eclipse.cdt.debug.core.CDIDebugModel;
import org.eclipse.cdt.debug.core.model.ICAddressBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICDynamicPrintf;
import org.eclipse.cdt.debug.core.model.ICFunctionBreakpoint;
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.cdt.debug.internal.ui.breakpoints.BreakpointsMessages;
import org.eclipse.cdt.debug.internal.ui.breakpoints.CBreakpointContext;
import org.eclipse.cdt.debug.internal.ui.breakpoints.CBreakpointPreferenceStore;
import org.eclipse.cdt.debug.ui.breakpoints.ICBreakpointContext;
import org.eclipse.cdt.debug.ui.preferences.ReadOnlyFieldEditor;
import org.eclipse.cdt.dsf.gdb.breakpoints.GDBDynamicPrintfUtils;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugModelProvider;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.contexts.IDebugContextProvider;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.IntegerFieldEditor;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.model.IWorkbenchAdapter;

/**
 * The preference page used to present the properties of a GDB dynamic printf as preferences. 
 */
public class GDBDynamicPrintfPropertyPage extends FieldEditorPreferencePage implements IWorkbenchPropertyPage {

	private class DynamicPrintfIntegerFieldEditor extends IntegerFieldEditor {

		public DynamicPrintfIntegerFieldEditor(String name, String labelText, Composite parent) {
			super(name, labelText, parent);
			setErrorMessage(Messages.PropertyPage_integer_negative);
		}

		/**
		 * @see IntegerFieldEditor#checkState()
		 */
		@Override
		protected boolean checkState() {
			Text control = getTextControl();
			if (!control.isEnabled()) {
				clearErrorMessage();
				return true;
			}
			return super.checkState();
		}

		/**
		 * Only store if the text control is enabled
		 * 
		 * @see FieldEditor#doStore()
		 */
		@Override
		protected void doStore() {
			Text text = getTextControl();
			if (text.isEnabled()) {
				super.doStore();
			}
		}

		/**
		 * Clears the error message from the message line if the error message is the error message from this field editor.
		 */
		@Override
		protected void clearErrorMessage() {
			if (getPage() != null) {
				String message = getPage().getErrorMessage();
				if ( message != null ) {
					if (getErrorMessage().equals(message)) {
						super.clearErrorMessage();
					}
				}
				else {
					super.clearErrorMessage();
				}
			}
		}
	}

	class DynamicPrintfStringFieldEditor extends StringFieldEditor {

		public DynamicPrintfStringFieldEditor(String name, String labelText, Composite parent) {
			super(name, labelText, parent);
		}

		/**
		 * @see StringFieldEditor#checkState()
		 */
		@Override
		protected boolean checkState() {
			Text control = getTextControl();
			if (!control.isEnabled()) {
				clearErrorMessage();
				return true;
			}
			return super.checkState();
		}

		@Override
		protected void doStore() {
			Text text = getTextControl();
			if (text.isEnabled()) {
				super.doStore();
			}
		}
		
		@Override
		protected void doLoad()  {
			String value = getPreferenceStore().getString(getPreferenceName());
            setStringValue(value);
		}

		/**
		 * Clears the error message from the message line if the error message is the error message from this field editor.
		 */
		@Override
		protected void clearErrorMessage() {
			if (getPage() != null) {
				String message = getPage().getErrorMessage();
				if ( message != null ) {
					if (getErrorMessage().equals(message)) {
						super.clearErrorMessage();
					}
				}
				else {
					super.clearErrorMessage();
				}
			}
		}
	}

	private class LabelFieldEditor extends ReadOnlyFieldEditor {
		private String fValue;

		public LabelFieldEditor(Composite parent, String title, String value) {
			super(title, title, parent);
			fValue = value;
		}

		@Override
		protected void doLoad() {
			if (textField != null) {
				textField.setText(fValue);
			}
		}
		@Override
		protected void doLoadDefault() {
			// nothing
		}

	}

	private BooleanFieldEditor fEnabled;

	private DynamicPrintfStringFieldEditor fCondition;

	private Text fIgnoreCountTextControl;
	private DynamicPrintfIntegerFieldEditor fLineEditor;
	private DynamicPrintfIntegerFieldEditor fIgnoreCount;
	
	/** 
	 * Indicates if the page currently aims to create
	 * a breakpoint that already exits.
	 */
	private boolean fDuplicateBreakpoint;

	
	private DynamicPrintfStringFieldEditor fPrintString;

	private IAdaptable fElement;

	/**
	 * The preference store used to interface between the dynamic printf and the 
	 * dynamic printf preference page.  This preference store is initialized only
	 * when the preference store cannot be retrieved from the preference 
	 * dialog's element.
	 * @see #getPreferenceStore()
	 */
	private CBreakpointPreferenceStore fDynamicPrintfPreferenceStore;

	public GDBDynamicPrintfPropertyPage() {
		super(GRID);
		noDefaultAndApplyButton();
	}

	@Override
	protected void createFieldEditors() {
		ICDynamicPrintf dprintf = getDprintf();
		createMainLabel(dprintf);
		createTypeSpecificLabelFieldEditors(dprintf);
		createEnabledField(getFieldEditorParent());
		createConditionEditor(getFieldEditorParent());
		createIgnoreCountEditor(getFieldEditorParent());
		createPrintStringEditor(getFieldEditorParent());
	}

	private void createMainLabel(ICDynamicPrintf dprintf) {
		addField(createLabelEditor(getFieldEditorParent(), 
				                   Messages.PropertyPage_Class,
				                   getDynamicPrintfMainLabel(dprintf)));
	}

	private void createTypeSpecificLabelFieldEditors(ICDynamicPrintf dprintf) {
		if (dprintf instanceof ICFunctionBreakpoint) {
		    createFunctionEditor(getFieldEditorParent());
		}
		else if (dprintf instanceof ICAddressBreakpoint) {
			String address = getPreferenceStore().getString(ICLineBreakpoint.ADDRESS);
			if (address == null || address.trim().length() == 0) {
				address = Messages.PropertyPage_NotAvailable;
			}
			addField(createLabelEditor(getFieldEditorParent(), Messages.PropertyPage_Address, address));
		}
		else { // LineDprintf
		    String fileName = getPreferenceStore().getString(ICBreakpoint.SOURCE_HANDLE);
			if (fileName != null) {
				addField(createLabelEditor(getFieldEditorParent(), Messages.PropertyPage_File, fileName));
			}
			int lNumber = getPreferenceStore().getInt(IMarker.LINE_NUMBER);
			if (lNumber > 0) {
				createLineNumberEditor(getFieldEditorParent());
			}
		}
	}

	private String getDynamicPrintfMainLabel(ICDynamicPrintf dprintf) {
	    IWorkbenchAdapter labelProvider = getElement().getAdapter(IWorkbenchAdapter.class);
	    if (labelProvider != null) {
	        return labelProvider.getLabel(getElement());
	    }
        // default main label is the label of marker type for the dynamic printf
        return CDIDebugModel.calculateMarkerType(dprintf);
	}
	
    protected void createFunctionEditor(Composite parent) {
    	ICDynamicPrintf dprintf = getDprintf();
        if (dprintf == null || dprintf.getMarker() == null) {
            DynamicPrintfStringFieldEditor expressionEditor = new DynamicPrintfStringFieldEditor(
                ICLineBreakpoint.FUNCTION, Messages.PropertyPage_FunctionName, parent);
            expressionEditor.setErrorMessage(Messages.PropertyPage_function_value_errorMessage);
            expressionEditor.setEmptyStringAllowed(false);
            addField(expressionEditor);
        } else {
            String function = getPreferenceStore().getString(ICLineBreakpoint.FUNCTION); 
            if (function == null) { 
                function = Messages.PropertyPage_NotAvailable;
            }
            addField(createLabelEditor(getFieldEditorParent(), Messages.PropertyPage_FunctionName, function));
        }
    }
	protected void createLineNumberEditor(Composite parent) {
		 String title = Messages.PropertyPage_LineNumber;
		 fLineEditor = new DynamicPrintfIntegerFieldEditor(IMarker.LINE_NUMBER, title, parent);
		 fLineEditor.setValidRange(1, Integer.MAX_VALUE);
		 fLineEditor.setErrorMessage(Messages.PropertyPage_lineNumber_errorMessage);
		 addField(fLineEditor);
	}
	
	protected void createEnabledField(Composite parent) {
		fEnabled = new BooleanFieldEditor(ICBreakpoint.ENABLED, Messages.PropertyPage_Enabled, parent);
		addField(fEnabled);
	}

	protected void createConditionEditor(Composite parent) {
		fCondition = new DynamicPrintfStringFieldEditor(ICBreakpoint.CONDITION, Messages.PropertyPage_Condition, parent);
		fCondition.setEmptyStringAllowed(true);
		fCondition.setErrorMessage(Messages.PropertyPage_InvalidCondition);
		addField(fCondition);
	}

	protected void createIgnoreCountEditor(Composite parent) {
		fIgnoreCount = new DynamicPrintfIntegerFieldEditor(ICBreakpoint.IGNORE_COUNT, Messages.PropertyPage_IgnoreCount, parent);
		fIgnoreCount.setValidRange(0, Integer.MAX_VALUE);
		fIgnoreCountTextControl = fIgnoreCount.getTextControl(parent);
		fIgnoreCountTextControl.setEnabled( getPreferenceStore().getInt(ICBreakpoint.IGNORE_COUNT) >= 0 );
		addField(fIgnoreCount);
	}

	protected void createPrintStringEditor(Composite parent) {
		fPrintString = new DynamicPrintfStringFieldEditor(ICDynamicPrintf.PRINTF_STRING, Messages.DynamicPrintfPropertyPage_PrintString, parent) {
			@Override
			protected boolean doCheckState() {
				GDBDynamicPrintfUtils.GDBDynamicPrintfString parsedStr = 
						new GDBDynamicPrintfUtils.GDBDynamicPrintfString(getTextControl().getText());
				
				boolean valid = parsedStr.isValid();
				if (!valid) {
					setErrorMessage(parsedStr.getErrorMessage());
				}
				
				return valid;
			}
		};
		addField(fPrintString);
	}

	@Override
	public boolean isValid() {
		// Don't allow to create a duplicate breakpoint
		return super.isValid() && !fDuplicateBreakpoint;
	}
	
	@Override
	public void propertyChange(PropertyChangeEvent event) {
		super.propertyChange(event);

		ICBreakpoint currentBp = getDprintf();
		if (!(currentBp instanceof ICFunctionBreakpoint) &&
				!(currentBp instanceof ICAddressBreakpoint)) {
			// Check for duplication of line dprintf
			if (event.getProperty().equals(FieldEditor.VALUE)) {
				if (super.isValid()) {
					// For every change, if all the fields are valid
					// we then check if we are dealing with a duplicate
					// breakpoint.
					boolean oldValue = fDuplicateBreakpoint;
					fDuplicateBreakpoint = isDuplicateBreakpoint();
					if (oldValue != fDuplicateBreakpoint) {
						if (fDuplicateBreakpoint) {
							setErrorMessage(BreakpointsMessages.getString("CBreakpointPropertyPage.breakpoint_already_exists_errorMessage")); //$NON-NLS-1$
						} else {
							setErrorMessage(null);
						}
						// update container state
						if (getContainer() != null) {
							getContainer().updateButtons();
						}
						// update page state
						updateApplyButton();
					}
				}		
			}
		}
	}
	
	private boolean isDuplicateBreakpoint() {
		String source = getPreferenceStore().getString(ICBreakpoint.SOURCE_HANDLE);
		int line = fLineEditor.getIntValue();

		// Look for any breakpoint (base bp class) that has the same source file and line number as what
		// is currently being inputed.  Careful not to compare with the current dprintf
		// in the case of modifying the properties of an existing dprintf; in
		// that case we of course have this particular dprintf at this file and line.
		ICBreakpoint currentBp = getDprintf();
		IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints();
		for (IBreakpoint bp : breakpoints) {
			if (!bp.equals(currentBp) && bp instanceof ICBreakpoint) {
				IMarker marker = bp.getMarker();
				if (marker != null) {
					String markerFile = marker.getAttribute(ICBreakpoint.SOURCE_HANDLE, ""); //$NON-NLS-1$
					int markerLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
					if (source.equals(markerFile) && line == markerLine) {
						// Woops, we already have another breakpoint at this file:line
						return true;
					}
				}
			}
		}
		return false;
	}
	
	protected FieldEditor createLabelEditor(Composite parent, String title, String value) {
		return new LabelFieldEditor(parent, title, value);
	}

	protected ICDynamicPrintf getDprintf() {
		IAdaptable element = getElement();
		if (element instanceof ICDynamicPrintf) {
		    return (ICDynamicPrintf)element;
		}
		
		if (element instanceof ICBreakpointContext) {
			ICBreakpoint breakpoint =((ICBreakpointContext)element).getBreakpoint();
			if (breakpoint instanceof ICDynamicPrintf) {
			    return (ICDynamicPrintf)breakpoint;
			}
			assert false : "Should always have a dprintf"; //$NON-NLS-1$
		}

		return element.getAdapter(ICDynamicPrintf.class);
	}

	protected Object getDebugContext() {
        IDebugContextProvider provider = getElement().getAdapter(IDebugContextProvider.class);
        if (provider != null) {
            ISelection selection = provider.getActiveContext();
            if (selection instanceof IStructuredSelection) {
                return ((IStructuredSelection) selection).getFirstElement();
            }
            return null;
        }
        return DebugUITools.getDebugContext();        
	}

	
	protected IResource getResource() {
        IAdaptable element = getElement();
        if (element instanceof ICDynamicPrintf) {
            IMarker marker = ((ICDynamicPrintf)element).getMarker();
            if (marker != null) {
                return marker.getResource();
            }
        } else if (element instanceof ICBreakpointContext) {
            return ((ICBreakpointContext)element).getResource();
        } 
        return null;
	}

	@Override
	public IPreferenceStore getPreferenceStore() {
	    IAdaptable element = getElement();
	    if (element instanceof ICBreakpointContext) {
	        return ((ICBreakpointContext)element).getPreferenceStore();
	    }

	    if (fDynamicPrintfPreferenceStore == null) {
	        CBreakpointContext bpContext = element instanceof CBreakpointContext ? 
	            (CBreakpointContext)element : null;
	        fDynamicPrintfPreferenceStore = new CBreakpointPreferenceStore(bpContext, null);
	    }
	    return fDynamicPrintfPreferenceStore;
	}

	@Override
	public boolean performCancel() {
	    IPreferenceStore store = getPreferenceStore();
	    if (store instanceof CBreakpointPreferenceStore) {
	        ((CBreakpointPreferenceStore)store).setCanceled(true);
	    }
	    return super.performCancel();
	}

	@Override
    public boolean performOk() {
        IPreferenceStore store = getPreferenceStore();
        if (store instanceof CBreakpointPreferenceStore) {
            ((CBreakpointPreferenceStore)store).setCanceled(false);
        }
        return super.performOk();
    }
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.IWorkbenchPropertyPage#getElement()
	 */
    @Override
	public IAdaptable getElement() {
		return fElement;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.IWorkbenchPropertyPage#setElement(org.eclipse.core.runtime.IAdaptable)
	 */
    @Override
	public void setElement(IAdaptable element) {
    	if (element instanceof ICBreakpoint) {
			fElement = new CBreakpointContext((ICBreakpoint)element, null);
		}
		else {
			fElement = element;
		}
	}

	protected String[] getDebugModelIds() {
        String[] debugModelIds = null;
        Object debugContext = getDebugContext();
        IDebugModelProvider debugModelProvider = (IDebugModelProvider)
            DebugPlugin.getAdapter(debugContext, IDebugModelProvider.class);
        if (debugModelProvider != null) {
            debugModelIds = debugModelProvider.getModelIdentifiers();
        } else if (debugContext instanceof IDebugElement) {
            debugModelIds = new String[] { ((IDebugElement)debugContext).getModelIdentifier() };
        }
        return debugModelIds;
	}
}
